# 02 引用和解引用以及常用的方法和特征 * **引用(Reference, `&`)**:指向内存中某个值的地址,它不拥有该数据的所有权。 * **解引用(Deref, `*`)**:通过 `*` 操作符访问引用所指向的底层真实数据。 * **自动解引用(Deref Coercion)**:Rust 编译器非常智能,在调用方法时,它会自动帮你做多次解引用。例如,`&String` 可以直接调用 `&str` 的方法。 * **常见特征(Traits)**: * `Copy`:数据按位拷贝(如基础整型、布尔型),栈上分配。 * `Clone`:显式复制(深拷贝),可能涉及堆内存分配。 * `Deref`:允许自定义类型的解引用行为。 ## 引用(Reference, `&`)与解引用(Deref, `*`) * **引用(Reference, `&`)**:指向内存中某个值的地址,它不拥有该数据的所有权。 * **解引用(Deref, `*`)**:通过 `*` 操作符访问引用所指向的底层真实数据。 * **自动解引用(Deref Coercion)**:Rust 编译器非常智能,在调用方法时,它会自动帮你做多次解引用。例如,`&String` 可以直接调用 `&str` 的方法。 ### 常用数据类型转换与解引用技巧(重点补充) 在 Rust 开发中,所有权和字符串/切片之间的转换非常频繁。掌握以下几种核心转换,能帮你避开绝大多数类型不匹配的编译错误: #### 1. `String` 转 `&str`(所有权转借用,极度常用) `String` 是堆上分配的可变字符串,而 `&str` 是不可变的字符串切片(通常指向堆上的某段数据或静态内存)。 * **方式一:通过自动解引用(Deref Coercion)** 当把 `String` 的引用传给需要 `&str` 的函数时,Rust 会自动转换: ```rust fn print_str(s: &str) { println!("{}", s); } let my_string = String::from("hello"); print_str(&my_string); // &String 自动变成了 &str ``` * **方式二:显式切片** ```rust let s = String::from("hello"); let slice: &str = &s[..]; // 取整个字符串的切片 ``` ### 2. `&str` 转 `String`(借用转所有权,堆上分配) 当你需要把一个固定的字符串字面量(`&str`)变成一个可以修改、可以拥有所有权的字符串时: * **使用 `.to_string()` 或 `.to_owned()` 或 `String::from()**`: ```rust let literal: &str = "hello"; let s1 = literal.to_string(); let s2 = String::from(literal); ``` ### 3. 数组/向量(Vec/Slice)与切片转换 * **`Vec` 转 `&[T]`(向量转切片)**: 类似于 `String` 到 `&str`,`Vec` 也可以自动解引用或通过切片语法转为切片: ```rust let v: Vec = vec![1, 2, 3]; let slice: &[i32] = &v; // 自动解引用为切片 ``` ## 常见特征(Traits) * `Copy`:数据按位拷贝(如基础整型、布尔型),栈上分配。 * `Clone`:显式复制(深拷贝),可能涉及堆内存分配。 * `Deref`:允许自定义类型的解引用行为(让你自己写的智能指针结构体也能像普通引用一样使用 `*` 和点号 `.method()` 访问)。 ### 为什么能够解引用?深入底层的 `Deref` 特征 刚才我们提到了 `Deref` 特征,这正是 Rust 能够实现“解引用(`*`)”和“自动解引用(Deref Coercion)”的幕后功臣。 在 Rust 中,解引用并不是凭空发生的,而是通过实现标准库中的 **`std::ops::Deref`** 和 **`std::ops::DerefMut`** 特征(Trait)来支持的。 --- ### 1. `Deref` 特征的真面目 在标准库中,`Deref` 特征的定义大致长这样: ```rust pub trait Deref { // 关联类型:解引用后得到的目标类型 type Target: ?Sized; // 核心方法:给定一个自身的不可变借用,返回目标类型的不可变借用 fn deref(&self) -> &Self::Target; } ``` 只要你为一个自定义结构体(比如你写的智能指针、包装类型)实现了 `Deref` 特征,Rust 编译器就会认可它:**“这个结构体可以被解引用”**。 --- ### 2. 一个自定义结构体实现 `Deref` 的完整示例 假设我们自己写了一个简易的智能指针 `MyBox`,它把数据存放在堆上。如果不实现 `Deref`,我们就必须写 `*(b.data)` 才能拿到数据;实现了之后,就可以直接用 `*b`: ```rust use std::ops::Deref; // 自定义一个包裹泛型 T 的结构体 struct MyBox(T); impl MyBox { fn new(x: T) -> MyBox { MyBox(x) } } // 为 MyBox 实现 Deref 特征 impl Deref for MyBox { type Target = T; // 指定解引用后目标类型是 T fn deref(&self) -> &Self::Target { &self.0 // 返回元组内部数据的引用 } } fn main() { let x = 5; let y = MyBox::new(x); // 奇迹发生了:*(y) 实际上在编译时被 Rust 自动翻译成了 *(y.deref()) assert_eq!(5, *y); } ``` --- ### 3. 幕后发生了什么?(编译器如何处理 `*y`) 当你写下 `*y` 时,如果 `y` 的类型实现了 `Deref` 特征,Rust 编译器实际上悄悄帮你执行了这一行代码: `*(y.deref())` 1. 先调用 `y.deref()`,拿到内部数据的引用(`&T`)。 2. 再用 `*` 把这个引用解开,拿到真实的值。 --- ### 4. 可变解引用:`DerefMut` 除了不可变的 `Deref`,如果你的包装类型不仅想“读”,还想允许通过 `*` 去**修改**内部的数据,就需要额外实现 **`DerefMut`** 特征: ```rust use std::ops::{Deref, DerefMut}; // DerefMut 依赖于 Deref impl DerefMut for MyBox { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } ``` 有了 `DerefMut`,你就可以写出 `*y = 10;` 这样修改底层数据的代码。 --- ### 5. 为什么 `String` 能自动变成 `&str`?(自动解引用的链条) 回到我们之前提到的 `String` 到 `&str` 的转换。其实标准库在底层就是这样为 `String` 实现 `Deref` 的: * `String` 实现了 `Deref`。 * 所以,当你把一个 `&String` 传给一个需要 `&str` 的函数时,Rust 编译器发现类型不匹配,它就会自动去查:**“这个类型有没有实现 `Deref`?”** * 发现 `String` 实现了 `Deref`,于是编译器自动调用了一次 `deref()`,把 `&String` 变成了 `&str`。 * 如果有多层包装(比如 `&MyBox` -> `&String` -> `&str`),Rust 的自动解引用强制转换(Deref Coercion)甚至会连续调用多次 `deref()`,直到类型完全匹配为止!